commonlibsse_ng\rel\module/
module_core.rs1use super::module_handle::{ModuleHandle, ModuleHandleError};
12use super::runtime::Runtime;
13use super::segment::{Segment, SegmentName};
14use crate::rel::version::{FileVersionError, Version, get_file_version};
15use snafu::ResultExt as _;
16use windows::Win32::System::Diagnostics::Debug::{
17 IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_WRITE, IMAGE_SECTION_CHARACTERISTICS,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Module {
23 pub filename: windows::core::HSTRING,
25 pub file_path: String,
27 pub(crate) segments: [Segment; 8],
29 pub version: Version,
31 pub base: ModuleHandle,
33 pub runtime: Runtime,
35}
36
37impl Module {
38 const SEGMENTS: [(&str, IMAGE_SECTION_CHARACTERISTICS); 8] = [
39 (".text", IMAGE_SCN_MEM_EXECUTE),
40 (".idata", IMAGE_SECTION_CHARACTERISTICS(0)),
41 (".rdata", IMAGE_SECTION_CHARACTERISTICS(0)),
42 (".data", IMAGE_SECTION_CHARACTERISTICS(0)),
43 (".pdata", IMAGE_SECTION_CHARACTERISTICS(0)),
44 (".tls", IMAGE_SECTION_CHARACTERISTICS(0)),
45 (".text", IMAGE_SCN_MEM_WRITE),
46 (".gfids", IMAGE_SECTION_CHARACTERISTICS(0)),
47 ];
48
49 #[cfg(feature = "test_on_ci")]
51 pub(crate) fn new_with_msvcrt() -> Result<Self, ModuleInitError> {
52 let filename = windows::core::h!("msvcrt.dll");
53 let module_handle = unsafe {
54 ModuleHandle::new(filename).map_err(|_| ModuleInitError::ModuleNameAndHandleNotFound)
55 }?;
56
57 Self::init_inner(filename.clone(), module_handle)
58 }
59
60 #[cfg(feature = "test_on_local")]
61 pub(crate) fn new_from_skyrim_exe() -> Result<Self, ModuleInitError> {
62 let path = crate::rel::module::get_skyrim_exe_path(Runtime::Ae)
63 .ok_or(ModuleInitError::ModuleNameAndHandleNotFound)?;
64 let path = windows::core::HSTRING::from(path.as_path());
65
66 let module_handle = unsafe {
67 windows::Win32::System::LibraryLoader::LoadLibraryW(&path)
68 .map_err(|_| ModuleInitError::ModuleNameAndHandleNotFound)
69 }?;
70 let module_handle =
71 ModuleHandle(unsafe { core::ptr::NonNull::new_unchecked(module_handle.0) });
72
73 Self::init_inner(path, module_handle)
74 }
75
76 pub fn new() -> Result<Self, ModuleInitError> {
86 use windows::Win32::System::Environment::GetEnvironmentVariableW;
87 use windows::core::{HSTRING, h};
88
89 #[inline]
90 fn get_module_name_from_skse() -> Option<(HSTRING, ModuleHandle)> {
91 let mut filename = vec![0; windows::Win32::Foundation::MAX_PATH as usize];
92 let filename_len =
93 unsafe { GetEnvironmentVariableW(h!("SKSE_RUNTIME"), Some(&mut filename)) }
94 as usize;
95
96 let is_failed = filename_len != filename.len() - 1 || filename_len == 0;
97 if is_failed {
98 return None;
99 }
100
101 let filename = HSTRING::from_wide(&filename);
102 let new_handle = unsafe { ModuleHandle::new(&filename).ok() }?;
104 Some((filename, new_handle))
105 }
106
107 #[inline]
108 fn get_module_handle_from_runtime() -> Option<(HSTRING, ModuleHandle)> {
109 #[cfg(feature = "tracing")]
110 tracing::info!(
111 "Failed to read the `SKSE_RUNTIME` environment variable. Trying to get it from Runtime exe (e.g. `SkyrimSE.exe`) instead..."
112 );
113
114 const RUNTIMES: [&windows::core::HSTRING; 2] =
115 [windows::core::h!("SkyrimSE.exe"), windows::core::h!("SkyrimVR.exe")];
116
117 let mut ret = None;
118 for runtime_name in RUNTIMES {
119 let module_handle = unsafe { ModuleHandle::new(runtime_name) };
121 if let Ok(new_handle) = module_handle {
122 ret = Some((runtime_name.clone(), new_handle));
123 break;
124 }
125 }
126
127 ret
128 }
129
130 let (filename, module_handle) = get_module_name_from_skse()
131 .or_else(get_module_handle_from_runtime)
132 .ok_or(ModuleInitError::ModuleNameAndHandleNotFound)?;
133
134 Self::init_inner(filename, module_handle)
135 }
136
137 #[inline]
138 fn init_inner(
139 filename: windows::core::HSTRING,
140 module_handle: ModuleHandle,
141 ) -> Result<Self, ModuleInitError> {
142 let segments = Self::load_segments(&module_handle).context(SegmentLoadFailedSnafu)?;
143 let (version, runtime) = Self::load_version(&filename).context(VersionLoadFailedSnafu)?;
144 let file_path = filename.to_string();
145
146 Ok(Self { filename, file_path, segments, version, base: module_handle, runtime })
147 }
148
149 #[inline]
159 pub const fn segment(&self, name: SegmentName) -> Segment {
160 self.segments[name as usize]
161 }
162
163 #[inline]
164 fn load_segments(module_handle: &ModuleHandle) -> Result<[Segment; 8], ModuleHandleError> {
165 use windows::Win32::System::Diagnostics::Debug::{
166 IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER,
167 };
168
169 let nt_header = module_handle.try_as_nt_header()?;
170 let section_header_offset = {
171 let optional_header_offset = core::mem::offset_of!(IMAGE_NT_HEADERS64, OptionalHeader);
172 optional_header_offset + nt_header.FileHeader.SizeOfOptionalHeader as usize
173 };
174
175 let section =
176 unsafe { (nt_header as *const IMAGE_NT_HEADERS64).byte_add(section_header_offset) }
177 .cast::<IMAGE_SECTION_HEADER>();
178 let section_len =
179 core::cmp::min(nt_header.FileHeader.NumberOfSections as usize, Self::SEGMENTS.len());
180
181 let mut segments = [Segment::const_default(); 8];
182 for i in 0..section_len {
183 let current_section = unsafe { &*section.add(i) };
184
185 let maybe_found = Self::SEGMENTS.iter().enumerate().find(|(_, elem)| {
186 let maybe_ascii = core::str::from_utf8(¤t_section.Name);
187 maybe_ascii.is_ok_and(|section_name| {
188 elem.0 != section_name
189 && ((current_section.Characteristics & elem.1)
190 != IMAGE_SECTION_CHARACTERISTICS(0))
191 })
192 });
193
194 if let Some((idx, _)) = maybe_found {
195 segments[idx] = Segment {
196 proxy_base: *module_handle,
197 address: current_section.VirtualAddress,
198 size: current_section.SizeOfRawData,
199 };
200 }
201 }
202 Ok(segments)
203 }
204
205 #[inline]
206 fn load_version(
207 file_path: &windows::core::HSTRING,
208 ) -> Result<(Version, Runtime), FileVersionError> {
209 let version = get_file_version(file_path)?;
210 let runtime = Runtime::from_version(&version);
211 Ok((version, runtime))
212 }
213}
214
215#[derive(Debug, Clone, snafu::Snafu, PartialEq, Eq)]
217pub enum ModuleInitError {
218 ModuleNameAndHandleNotFound,
220 SegmentLoadFailed { source: crate::rel::module::ModuleHandleError },
222
223 #[snafu(display("Failed to load module version"))]
225 VersionLoadFailed { source: crate::rel::version::FileVersionError },
226}
227
228#[cfg(test)]
229mod tests {
230 #[cfg(feature = "test_on_ci")]
231 #[cfg_attr(miri, ignore)]
232 #[test]
233 fn test_module_init() {
234 use super::*;
235
236 match dbg!(Module::new_with_msvcrt()) {
240 Ok(module) => {
241 assert!(!module.file_path.is_empty());
242 assert!(!module.filename.is_empty());
243 assert_eq!(module.runtime, Runtime::Se);
244 }
245 Err(err) => panic!("Failed to initialize module: {err}"),
246 }
247 }
248}